package com.stu.book.java8sz2.char16;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Classname ShopPrice
 * @Version 1.0.0
 * @Date 2022/2/19 11:48
 * @Created by yd
 */
public class ShopPrice {

    List<Shop> shops = Arrays.asList(new Shop("BestPrice"), new Shop("LetsSaveBig"),
            new Shop("MyFavoriteShop"), new Shop("BuyItAll"));
    private final Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100),
            (Runnable r) -> {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                return thread;
            }
    );

    public static void main(String[] args) {
        Shop shop = new Shop("BestShop");
        long start = System.nanoTime();
        Future<Double> future = shop.getPriceAsync("my favorite product");
        long invocationTime = ((System.nanoTime() - start) / 1_000_000);
        System.out.println("Invocation returned after " + invocationTime + " mesecs");
        //执行其他任务
        try {
            //从Future获取，如果价格位置会阻塞
            Double aDouble = future.get();
            System.out.println("ShopPrice.main" + aDouble);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
        System.out.println("Price returned after " + retrievalTime + " msecs");
    }

    private static double calculatrPrice(String product) {
        delay();
        return ThreadLocalRandom.current().nextDouble() * product.charAt(0) + product.charAt(1);
    }

    public static void delay() {
        try {
            TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(2));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 以最简单的方式实现Discount服务
    public List<String> findPrices(String product) {
        return shops.stream()
                .map(shop -> shop.getPrice(product))
                .map(Discount.Quote::parse)
                .map(Discount::applyDiscount)
                .collect(Collectors.toList());
    }

    public List<String> findPricesCompletableFuture(String product) {
        Stream<CompletableFuture<String>> futureStream = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor))
                .map(future -> future.thenApply(Discount.Quote::parse))
                .map(future -> future.thenCompose(quote ->
                        CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor)));
        return futureStream.map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    public List<String> findPricess(String product) {
        List<CompletableFuture<String>> futures = shops
                .stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))))
                .collect(Collectors.toList());
        return futures.stream()
                //等待所有操作完成
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

    public List<String> findPricesOlds(String product) {
        return shops.stream()
                .map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product)))
                .collect(Collectors.toList());
    }

    public void findPriceCompletableFuture(String product) {
        List<CompletableFuture<String>> priceFutures = shops
                .stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))))
                .collect(Collectors.toList());
    }

    //使用并行流
    public List<String> findPricesPara(String product) {
        return shops.parallelStream()
                .map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product)))
                .collect(Collectors.toList());
    }

    /**
     * 同步方法
     */
    public double getPriceOldss(String product) {
        return calculatrPrice(product);
    }

    public static class Discount {
        public static String applyDiscount(Quote quote) {
            return quote.getShopName() + " price is " +
                    Discount.apply(quote.getPrice(),
                            quote.getDiscountCode());
        }

        private static double apply(double price, Code code) {
            delay();
            return (price * (100 - code.percentage) / 100);
        }

        public enum Code {
            NONE(0), SILVER(5), GOLD(10), PLATINUM(15), DIAMOND(20);
            private final int percentage;

            Code(int percentage) {
                this.percentage = percentage;
            }
        }

        public static class Quote {
            private final String shopName;
            private final double price;
            private final Discount.Code discountCode;

            public Quote(String shopName, double price, Discount.Code code) {
                this.shopName = shopName;
                this.price = price;
                this.discountCode = code;
            }

            public static Quote parse(String s) {
                String[] split = s.split(":");
                String shopName = split[0];
                double price = Double.parseDouble(split[1]);
                Discount.Code discountCode = Discount.Code.valueOf(split[2]);
                return new Quote(shopName, price, discountCode);
            }

            public String getShopName() {
                return shopName;
            }

            public double getPrice() {
                return price;
            }

            public Discount.Code getDiscountCode() {
                return discountCode;
            }
        }
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Shop {
        private String name;

        /**
         * 异步方法
         */
        public Future<Double> getPriceAsync(String product) {
            return CompletableFuture.supplyAsync(() -> calculatrPrice(product));
        }

        public Future<Double> getPriceAsyncOls(String product) {
            CompletableFuture<Double> future = new CompletableFuture<>();
            new Thread(() -> {
                try {
                    future.complete(calculatrPrice(product));
                } catch (Exception e) {
                    //抛出失败的异常
                    future.completeExceptionally(e);
                }
            }).start();
            return future;
        }

        public String getPrice(String product) {
            double price = calculatrPrice(product);
            Discount.Code code = Discount.Code.values()[ThreadLocalRandom.current().nextInt(Discount.Code.values().length)];
            return String.format("%s:%.2f:%s", name, price, code);
        }
    }
}
